using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace etcetera.Mvc.UI.GoogleMap
{
    /// <summary>
    /// 
    /// </summary>
    public class GoogleMap : IGoogleMap
    {
        private bool _enableScrollZoom;
        private MapZoomControlType _mapZoomControlType;
        private int _zoomLevel;
        private bool _enableMapTypeControl;
        private MapType _mapType;
        private MapLanguage _mapLanguage;
        private string _apiKey;
        private string _coordinates;
        private string _id;
        private int _width = 640;
        private int _height = 480;
        private bool _enableOverviewMap;
        private IEnumerable<Marker> _markers = new List<Marker>();
        private IDictionary<string, object> _attributes = new Dictionary<string, object>();

        public GoogleMap()
        {
            _mapZoomControlType = MapZoomControlType.SmallZoomControl;
            _zoomLevel = 8;
            _mapType = MapType.Map;
            _mapLanguage = MapLanguage.en;
            _coordinates = "47,8";
            _id = Guid.NewGuid().ToString();
        }

        /// <summary>
        /// Gets the Google Maps script URL required to include a script into the page.
        /// </summary>
        private string GMapScriptUrl
        {
            get
            {
                // If a language has been set, add it to the API URL
                // For more information, see http://code.google.com/support/bin/answer.py?answer=60737&useful=0&show_useful=1
                string sLang = string.Empty;

                if (_mapLanguage != MapLanguage.en)
                    sLang = string.Format("&hl={0}", _mapLanguage);

                return string.Format("http://maps.google.com/maps?file=api&amp;v=2&amp;key={0}{1}", _apiKey, sLang);
            }
        }

        public IGoogleMap WithId(string id)
        {
            _id = id;
            return this;
        }

        public IGoogleMap Width(int width)
        {
            _width = width;
            return this;
        }

        public IGoogleMap Height(int height)
        {
            _height = height;
            return this;
        }

        /// <summary>
        /// Sets the Google map API key. Get your own key at http://code.google.com/apis/maps/signup.html
        /// </summary>
        public IGoogleMap ApiKey(string apiKey)
        {
            _apiKey = apiKey;
            return this;
        }

        public IGoogleMap Language(MapLanguage mapLanguage)
        {
            _mapLanguage = mapLanguage;
            return this;
        }

        /// <summary>
        /// The coordinate which the map should be centered on. It must be in the format latitude, longitude like "42.567, 8.014"
        /// </summary>
        public IGoogleMap Coordinates(string coordinates)
        {
            _coordinates = coordinates;
            return this;
        }

        /// <summary>
        /// Sets whether zooming the map with the mouse wheel is enabled.
        /// </summary>
        public IGoogleMap ScrollZoom()
        {
            _enableScrollZoom = true;
            return this;
        }

        /// <summary>
        /// Gets or sets the map zoom level. The number must be between 1 (earth) and 17 (street level).
        /// </summary>
        /// <param name="zoomLevel">The zoom level.</param>
        /// <returns></returns>
        public IGoogleMap ZoomLevel(int zoomLevel)
        {
            if (zoomLevel < 1)
                zoomLevel = 1;
            if (zoomLevel > 17)
                zoomLevel = 17;
            
            _zoomLevel = zoomLevel;
            return this;
        }

        public IGoogleMap ZoomControlType(MapZoomControlType mapZoomControlType)
        {
            _mapZoomControlType = mapZoomControlType;
            return this;
        }

        public IGoogleMap EnableMapType(MapType defaultMapType)
        {
            _enableMapTypeControl = true;
            _mapType = defaultMapType;
            return this;
        }

        public IGoogleMap EnableOverviewMap()
        {
            _enableOverviewMap = true;
            return this;
        }

        public IGoogleMap Markers(IEnumerable<Marker> markers)
        {
            _markers = markers;
            return this;
        }

        /// <summary>
        /// Converts the specified attributes dictionary of key-value pairs into a string of HTML attributes. 
        /// </summary>
        /// <returns></returns>
        private static string BuildHtmlAttributes(IDictionary<string, object> attributes)
        {
            if (attributes == null || attributes.Count == 0)
            {
                return string.Empty;
            }

            const string attributeFormat = "{0}=\"{1}\"";

            return string.Join(" ", attributes.Select(pair => string.Format(attributeFormat, pair.Key, pair.Value)).ToArray());
        }

        /// <summary>
        /// Creates the dynamic javascript based on the control values.
        /// </summary>
        /// <returns>the dynamic javascript body without script tags</returns>
        private string GetDynamicMapScript()
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("<script type=\"text/javascript\">$(document).ready(function() { initialize(); }); $(document).unload(function() { GUnload(); } );");
            sb.AppendLine("function initialize() { if (GBrowserIsCompatible()) { ");
            sb.AppendFormat("var map = new GMap2(document.getElementById(\"{0}\"));", _id);
            sb.AppendFormat("map.setCenter(new GLatLng({0}), {1});", _coordinates, _zoomLevel);

            switch (_mapZoomControlType)
            {
                case MapZoomControlType.None:
                    sb.Append("map.addControl(new GSmallMapControl());");
                    break;
                case MapZoomControlType.LargeMapControl:
                    sb.Append("map.addControl(new GLargeMapControl());");
                    break;
                case MapZoomControlType.LargeMapControl3D:
                    sb.Append("map.addControl(new GLargeMapControl3D());");
                    break;
                case MapZoomControlType.SmallMapControl:
                    sb.Append("map.addControl(new GSmallMapControl());");
                    break;
                case MapZoomControlType.SmallZoomControl:
                    sb.Append("map.addControl(new GSmallZoomControl());"); 
                    break;
                case MapZoomControlType.SmallZoomControl3D:
                    sb.Append("map.addControl(new GSmallZoomControl3D());"); 
                    break;
                default:
                    sb.Append("map.addControl(new GSmallMapControl());");
                    break;
            }

            if (_enableMapTypeControl)
            {
                sb.Append("map.addControl(new GMapTypeControl());");
                switch (_mapType)
                {
                    case MapType.Satellite:
                        sb.Append("map.setMapType(G_SATELLITE_MAP);");
                        break;
                    case MapType.Hybrid:
                        sb.Append("map.setMapType(G_HYBRID_MAP);");
                        break;
                }
            }

            if (_enableScrollZoom)
                sb.Append("map.enableScrollWheelZoom();");

            if (_enableOverviewMap)
                sb.Append("map.addControl(new GOverviewMapControl());");

            // Create all markers
            foreach (Marker marker in _markers)
            {
                if (!string.IsNullOrEmpty(marker.Coordinates))
                {
                    string infoText = string.IsNullOrEmpty(marker.InfoText) ? string.Empty : marker.InfoText;
                    infoText = infoText.Replace("'", @"\'");
                    int showBubble = marker.ShowInfoWindow ? 1 : 0;
                    sb.AppendFormat("var marker = createMarker(map, new GLatLng({0}), '{1}', {2});", marker.Coordinates, infoText, showBubble);
                }
            }
            
            sb.AppendLine("} }");

            sb.AppendLine("function createMarker(map, point, infoHtml, showInfo) { var marker; if (map && point) { marker = new GMarker(point); map.addOverlay(marker); if (infoHtml.length) { setMarkerInfoWindow(marker, infoHtml, showInfo); } } return marker; }");
            sb.AppendLine("function setMarkerInfoWindow(marker, infoHtml, showInfo) { GEvent.addListener(marker, \"click\", function() { marker.openInfoWindowHtml(infoHtml); }); if (showInfo) { marker.openInfoWindowHtml(infoHtml); } };");

            sb.AppendLine("</script>");

            return sb.ToString();
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            // Emit the GMap script
            sb.AppendFormat("<script type=\"text/javascript\" src=\"{0}\"></script>", GMapScriptUrl);

            string id = string.IsNullOrEmpty(_id) ? string.Empty : string.Format(" id='{0}'", _id);
            string attrs = BuildHtmlAttributes(_attributes);
            const string gmapClass = "gmap";

            string styleFormat = string.Format("style=\"width:{0}px;height:{1}px\"", _width, _height);

            if (_attributes.ContainsKey("class"))
            {
                // Merge the CSS class names
                sb.AppendFormat("<div class='{0} {1}'{2} {3}>", gmapClass, _attributes["class"], id, styleFormat);
            }
            else
            {
                if (attrs.Length > 0)
                    sb.AppendFormat("<div class='{0}'{1} {2} {3}>", gmapClass, id, attrs, styleFormat);
                else
                    sb.AppendFormat("<div class='{0}'{1} {2}>", gmapClass, id, styleFormat);
            }

            sb.Append("</div>");

            // Render the init JavaScript
            string sScript = GetDynamicMapScript();
            sb.Append(sScript);
            
            return sb.ToString();
        }
    }
}